﻿using System;
using Microsoft.SPOT;
using System.Text;
using System.IO;
using Microsoft.SPOT.Hardware;
using System.Xml;

namespace G2Tech.CurrentCost
{
    /// <summary>
    /// Current Cost Manager
    /// It gets the input bytes coming from a serial comunications
    /// and raise events when current cost data is parsed correctly!
    /// </summary>
    public class CurrentCostManager : ICurrentCostManager
    {
        #region private fields
        
        private object _serialDataManagementLock = new object();
        private byte[] NewLineBytes = new byte[] { 0x0D, 0x0A };
        private byte[] _currentBuffer = null;  

        #endregion

        #region public methods
        
        /// <summary>
        /// Feed data from an external serial source connected to a Current Cost object.
        /// </summary>
        /// <param name="receivedData"></param>
        public void FeedData(byte[] receivedData)
        {
            lock (_serialDataManagementLock)
            {
                int currentBufferLenght = 0;

                if (_currentBuffer != null)
                {
                    currentBufferLenght = _currentBuffer.Length;
                }

                byte[] newBuffer = new byte[currentBufferLenght + receivedData.Length];

                if (_currentBuffer != null)
                {
                    _currentBuffer.CopyTo(newBuffer, 0);
                }

                receivedData.CopyTo(newBuffer, currentBufferLenght);

                _currentBuffer = newBuffer;

                Debug.GC(false);

                parseCurrentBuffer();
            }
        }

        #endregion

        #region public events
        
        /// <summary>
        /// On Data Parsed
        /// </summary>
        public event CurrentCostDataParsedEventHandler DataParsed;

        #endregion  

        #region event delegates

        /// <summary>
        /// Current Cost Data Parsed Event Handler
        /// </summary>
        /// <param name="systemConfiguration"></param>
        /// <returns></returns>
        public delegate void CurrentCostDataParsedEventHandler(CurrentCostData currentCostData);

        #endregion


        #region private stuff

        /// <summary>
        /// parse current buffer content... in case we found the right data 
        /// </summary>
        private void parseCurrentBuffer()
        {
            var newLineIndex = searchNewLineInBuffer(_currentBuffer);

            if (newLineIndex > -1)
            {
                //ehy this lines contains an entire row! (and may it be even more!)
                byte[] messageBuffer;
                byte[] remainingBuffer;

                splitBuffer(_currentBuffer, out messageBuffer, out remainingBuffer, newLineIndex);

                _currentBuffer = remainingBuffer;

                processBuffer(messageBuffer);
            }
        }

        private void splitBuffer(byte[] sourceBuffer, out byte[] messageBuffer, out byte[] remainingBuffer, int newLineIndex)
        {
            messageBuffer = Utility.ExtractRangeFromArray(sourceBuffer, 0, newLineIndex);

            if (sourceBuffer.Length > newLineIndex + 2)
            {
                remainingBuffer = Utility.ExtractRangeFromArray(sourceBuffer, (newLineIndex + 2), sourceBuffer.Length - (newLineIndex + 2));
            }
            else
            {
                remainingBuffer = new byte[0];
            }
        }

        /// <summary>
        /// Search New Line marker in the current buffer
        /// </summary>
        /// <param name="_currentBuffer"></param>
        /// <returns></returns>
        private int searchNewLineInBuffer(byte[] buffer)
        {
            int index = -1;

            if (buffer.Length > 2)
            {
                for (int searchIndex = buffer.Length - 2; searchIndex >= 0; searchIndex--)
                {
                    byte[] testBytes = Utility.ExtractRangeFromArray(buffer, searchIndex, 2);

                    if (testBytes[0] == NewLineBytes[0] && testBytes[1] == NewLineBytes[1])
                    {
                        //found a \r\n
                        index = searchIndex;
                    }
                }
            }

            return index;
        }

        /// <summary>
        /// Try to Process the founded line. should be an xml!
        /// </summary>
        /// <param name="foundedLine"></param>
        private void processBuffer(byte[] messageBuffer)
        {
            MemoryStream memoryStream = new MemoryStream(messageBuffer);

            //raise OnDataParsed on parsed data.....
            var xml = System.Xml.XmlReader.Create(memoryStream);

            bool isValidMessage = true;

            bool isRealTimeData = false;

            //historical data for now is not parsed at all!

            string sourceModel = null;

            double temperature = double.NaN;

            string sensorNumber = null;
            string sensorId = null;
            string sensorType = null;

            bool waitForCh1 = false;
            int ch1 = 0;
            bool waitForCh2 = false;
            int ch2 = 0;
            bool waitForCh3 = false;
            int ch3 = 0;

            try
            {
                while (isValidMessage && xml.Read())
                {
                    if (xml.NodeType == XmlNodeType.Element && xml.Name == "src")
                    {
                        sourceModel = xml.ReadString();
                    }

                    #region Real Time Data Parsing

                    if (xml.NodeType == XmlNodeType.Element && xml.Name == "tmpr")
                    {
                        isRealTimeData = true;

                        string temperatureString = xml.ReadString();
                        temperature = double.Parse(temperatureString);
                    }

                    if (isRealTimeData)
                    {

                        if (xml.NodeType == XmlNodeType.Element && xml.Name == "sensor")
                        {
                            sensorNumber = xml.ReadString();
                        }

                        if (xml.NodeType == XmlNodeType.Element && xml.Name == "type")
                        {
                            sensorType = xml.ReadString();
                        }

                        if (xml.NodeType == XmlNodeType.Element && xml.Name == "id")
                        {
                            sensorId = xml.ReadString();
                        }

                        if (xml.NodeType == XmlNodeType.Element && xml.Name == "ch1")
                        {
                            waitForCh1 = true;
                            waitForCh2 = false;
                            waitForCh3 = false;
                        }

                        if (xml.NodeType == XmlNodeType.Element && xml.Name == "ch2")
                        {
                            waitForCh1 = false;
                            waitForCh2 = true;
                            waitForCh3 = false;
                        }

                        if (xml.NodeType == XmlNodeType.Element && xml.Name == "ch3")
                        {
                            waitForCh1 = false;
                            waitForCh2 = false;
                            waitForCh3 = true;
                        }

                        if (xml.NodeType == XmlNodeType.Element && xml.Name == "watts")
                        {
                            string wattsString = xml.ReadString();

                            if (waitForCh1)
                                ch1 = int.Parse(wattsString);

                            if (waitForCh2)
                                ch2 = int.Parse(wattsString);

                            if (waitForCh3)
                                ch3 = int.Parse(wattsString);
                        }

                        /*
                                <msg>                            start of message
                                   <src>CC128-v0.11</src>        source and software version
                                   <dsb>00089</dsb>              days since birth, ie days run
                                   <time>13:02:39</time>         24 hour clock time as displayed
                                   <tmpr>18.7</tmpr>             temperature as displayed
                                   <sensor>1</sensor>            Appliance Number as displayed
                                   <id>01234</id>                radio ID received from the sensor
                                   <type>1</type>                sensor Type, "1" = electricity
                                   <ch1>                         sensor channel
                                      <watts>00345</watts>       data and units
                                   </ch1>
                                   <ch2>
                                      <watts>02151</watts>
                                   </ch2>
                                   <ch3>
                                      <watts>00000</watts>
                                   </ch3>
                                </msg> 
                        */
                    }

                    #endregion
                }

                if (!isRealTimeData)
                {
                    isValidMessage = false;
                }
            }
            catch (Exception ex)
            {
                Debug.Print("Bad XML Message!!! " + ex.Message);
            }

            if (isRealTimeData)
            {
                //now create the appliance data update...
                CurrentCostData data = new CurrentCostData();

                data.TimeStamp = DateTime.UtcNow;

                data.Model = sourceModel;
                data.ApplianceNumber = sensorNumber;
                data.ApplianceId = sensorId;
                data.Temperature = temperature;
                data.Ch1 = ch1;
                data.Ch2 = ch2;
                data.Ch3 = ch3;

                if (DataParsed != null)
                {
                    DataParsed(data);
                }
            }
        }

        #endregion

        #region current cost XML samples
        
        /*
         * 
         * 
        <msg>                            start of message
           <src>CC128-v0.11</src>        source and software version
           <dsb>00089</dsb>              days since birth, ie days run
           <time>13:02:39</time>         24 hour clock time as displayed
           <tmpr>18.7</tmpr>             temperature as displayed
           <sensor>1</sensor>            Appliance Number as displayed
           <id>01234</id>                radio ID received from the sensor
           <type>1</type>                sensor Type, "1" = electricity
           <ch1>                         sensor channel
              <watts>00345</watts>       data and units
           </ch1>
           <ch2>
              <watts>02151</watts>
           </ch2>
           <ch3>
              <watts>00000</watts>
           </ch3>
        </msg>  
         * 
         * 
         * 
         * 
         * 
        <msg>                            start of message
           <src>CC128-v0.11</src>        [as for Real-Time]
           <dsb>00089</dsb>              [as for Real-Time]
           <time>13:10:50</time>         [as for Real-Time]
           <hist>                        start of history
              <dsw>00032</dsw>           days since wipe of history
              <type>1</type>             sensor Type, "1" = electricity
              <units>kwhr</units>        units of data eg KWHr
              <data>                     start of data burst
                 <sensor>0</sensor>      historic Appliance Number
                 <h024>001.1</h024>      h="hours", 24="22 to 24 hrs ago"
                 <h022>000.9</h022>      h="hours", 22="20 to 22 hrs ago"
                 <h020>000.3</h020>      h="hours", 18="20 to 20 hrs ago"
                 <h018>000.4</h018>      h="hours", 22="16 to 18 hrs ago"
              </data>
              ...                        [Sensor 1 to 8 data here]
              <data>
                 <sensor>9</sensor>
                 <units>kwhr</units>
                 <h024>000.0</h024>
                 <h022>000.0</h022>
                 <h020>000.0</h020>
                 <h018>000.0</h018>
              </data>
           </hist>
        </msg> 
         * 
         * 
         *
        <msg>
           <src>CC128-v0.11</src>
           <dsb>00089</dsb>
           <time>13:10:50</time>
           <hist>
              <dsw>00032</dsw>
              <type>1</type>
              <units>kwhr</units>
              <data>
                 <sensor>0</sensor>
                 <units>kwhr</units>
                 <d055>012.9</d055>      d="days", 055="55 days ago"
                 <d054>011.0</d054>
                 <d053>014.3</d053>
                 <d052>019.5</d052>
              </data>
              ...                        [Sensor 1 to 8 data here]
              <data>
                 <sensor>9</sensor>
                 <units>kwhr</units>
                 <d055>000.0</d055>
                 <d054>000.0</d054>
                 <d053>000.0</d053>
                 <d052>000.0</d052>
              </data>
             </hist>
        </msg>
         * 
         * 
         * 
         * 
         * 
        <msg>
           <src>CC128-v0.11</src>
           <dsb>00089</dsb>
           <time>13:10:50</time>
           <hist>
              <dsw>00032</dsw>
              <type>1</type>
              <units>kwhr</units>
              <data>
                 <sensor>0</sensor>
                 <units>kwhr</units>    
                 <m002>257.0</m002>      m="month", 002="2 months ago"
                 <m001>340.0</m001>
              </data>
              ...                        [Sensor 1 to 8 data here]
              <data>
                 <sensor>9</sensor>
                 <units>kwhr</units>
                 <m002>000.0</m002>
                 <m001>000.0</m001>
              </data>
             </hist>
        </msg>
         * 
         * 
         */
        
        #endregion
    }
}
